# Copyright 2004-2005 Daniel Henninger <jadestorm@nc.rr.com>
# Licensed for distribution under the GPL version 2, check COPYING for details

import utils
from tlib.twistwrap import Element, jid
from twisted.internet.defer import Deferred
from twisted.internet import reactor
import sys
import config
import legacy
from debug import LogEvent, INFO, WARN, ERROR
import lang
import globals


class ServerDiscovery:
	""" Handles everything IQ related. You can send IQ stanzas and receive a Deferred
	to notify you when a response comes, or if there's a timeout.
	Also manages discovery for server & client """
 	 
	# TODO rename this file & class to something more sensible

	def __init__ (self, pytrans):
		LogEvent(INFO)
		self.pytrans = pytrans
		self.identities = {}
		self.features = {}
		self.nodes = {}
		self.deferredIqs = {} # A dict indexed by (jid, id) of deferreds to fire
		
		self.addFeature(globals.DISCO, None, config.jid)
		self.addFeature(globals.DISCO, None, config.confjid)
		self.addFeature(globals.DISCO, None, "USER")

	def sendIq(self, el, timeout=15):
		""" Used for sending IQ packets.
		The id attribute for the IQ will be autogenerated if it is not there yet.
		Returns a deferred which will fire with the matching IQ response as it's sole argument. """
		def checkDeferred():
			if not d.called:
				d.errback()
				del self.deferredIqs[(jid, ID)]

		jid = el.getAttribute("to")
		ID = el.getAttribute("id")
		if not ID:
			ID = self.pytrans.makeMessageID()
			el.attributes["id"] = ID
		self.pytrans.send(el)
		d = Deferred()
		self.deferredIqs[(jid, ID)] = d
		reactor.callLater(timeout, checkDeferred)
		return d

	def addIdentity(self, category, ctype, name, jid):
		""" Adds an identity to this JID's discovery profile. If jid == "USER" then AIM users will get this identity. """
		LogEvent(INFO)
		if not self.identities.has_key(jid):
			self.identities[jid] = []
		self.identities[jid].append((category, ctype, name))
	
	def addFeature(self, var, handler, jid):
		""" Adds a feature to this JID's discovery profile. If jid == "USER" then AIM users will get this feature. """
		LogEvent(INFO)
		if not self.features.has_key(jid):
			self.features[jid] = []
		self.features[jid].append((var, handler))

	def addNode(self, node, handler, name, jid, rootnode):
		""" Adds a node to this JID's discovery profile. If jid == "USER" then AIM users will get this node. """
		LogEvent(INFO)
		if not self.nodes.has_key(jid):
			self.nodes[jid] = {}
		self.nodes[jid][node] = (handler, name, rootnode)

	def onIq(self, el):
		""" Decides what to do with an IQ """
		fro = el.getAttribute("from")
		to = el.getAttribute("to")
		ID = el.getAttribute("id")
		iqType = el.getAttribute("type")
		ulang = utils.getLang(el)
		try: # StringPrep
			froj = jid.JID(fro)
			to = jid.JID(to).full()
		except Exception, e:
			LogEvent(INFO, "", "Dropping IQ because of stringprep error")

		# Check if it's a response to a sent IQ
		if self.deferredIqs.has_key((fro, ID)) and (iqType == "error" or iqType == "result"):
			LogEvent(INFO, "", "Doing callback")
			self.deferredIqs[(fro, ID)].callback(el)
			del self.deferredIqs[(fro, ID)]
			return

		if not (iqType == "get" or iqType == "set"): return # Not interested

		LogEvent(INFO, "", "Looking for handler")

		for query in el.elements():
			xmlns = query.defaultUri
			node = query.getAttribute("node")

			if xmlns.startswith(globals.DISCO) and node:
				if self.nodes.has_key(to) and self.nodes[to].has_key(node) and self.nodes[to][node][0] != None:
					self.nodes[to][node][0](el)
					return
				else:
					# If the node we're browsing wasn't found, fall through and display the root disco
					self.sendDiscoInfoResponse(to=fro, ID=ID, ulang=ulang, jid=to)
					return
			elif xmlns == globals.DISCO_INFO:
				self.sendDiscoInfoResponse(to=fro, ID=ID, ulang=ulang, jid=to)
				return
			elif xmlns == globals.DISCO_ITEMS:
				self.sendDiscoItemsResponse(to=fro, ID=ID, ulang=ulang, jid=to)
				return

			if to.find('@') > 0:
				searchjid = "USER"
			else:
				searchjid = to

			for (feature, handler) in self.features.get(searchjid, []):
					if feature == xmlns and handler:
						LogEvent(INFO, "", "Handler found")
						handler(el)
						return

			# Still hasn't been handled
			LogEvent(WARN, "", "Unknown Iq Request")
			self.sendIqError(to=fro, fro=to, ID=ID, xmlns=xmlns, etype="cancel", condition="feature-not-implemented")

	def sendDiscoInfoResponse(self, to, ID, ulang, jid):
		""" Send a service discovery disco#info stanza to the given 'to'. 'jid' is the JID that was queried. """
		LogEvent(INFO)
		iq = Element((None, "iq"))
		iq.attributes["type"] = "result"
		iq.attributes["from"] = jid
		iq.attributes["to"] = to
		if ID:
			iq.attributes["id"] = ID
		query = iq.addElement("query")
		query.attributes["xmlns"] = globals.DISCO_INFO
		
		searchjid = jid
		if jid.find('@') > 0: searchjid = "USER"

		# Add any identities
		for (category, ctype, name) in self.identities.get(searchjid, []):
			#debug.log("Found identity %s %s %s" % (category, ctype, name))
			identity = query.addElement("identity")
			identity.attributes["category"] = category
			identity.attributes["type"] = ctype
			identity.attributes["name"] = name
		
		# Add any supported features
		for (var, handler) in self.features.get(searchjid, []):
			#debug.log("Found feature %s" % (var))
			feature = query.addElement("feature")
			feature.attributes["var"] = var

		self.pytrans.send(iq)

	def sendDiscoItemsResponse(self, to, ID, ulang, jid):
		""" Send a service discovery disco#items stanza to the given 'to'. 'jid' is the JID that was queried. """
		LogEvent(INFO)
		iq = Element((None, "iq"))
		iq.attributes["type"] = "result"
		iq.attributes["from"] = jid
		iq.attributes["to"] = to
		if ID:
			iq.attributes["id"] = ID
		query = iq.addElement("query")
		query.attributes["xmlns"] = globals.DISCO_ITEMS

		searchjid = jid
		if jid.find('@') > 0: searchjid = "USER"

		for node in self.nodes.get(searchjid, []):
			handler, name, rootnode = self.nodes[jid][node]
			if rootnode:
				#debug.log("Found node %s" % (node))
				name = lang.get(name, ulang)
				item = query.addElement("item")
				item.attributes["jid"] = jid
				item.attributes["node"] = node
				item.attributes["name"] = name
		
		self.pytrans.send(iq)
	
	def sendIqError(self, to, fro, ID, xmlns, etype, condition):
		""" Sends an IQ error response. See the XMPP RFC for details on the fields. """
		#debug.log("Sending IQ Error: %s %s %s %s %s" % (to,fro,xmlns,etype,condition))
		el = Element((None, "iq"))
		el.attributes["to"] = to
		el.attributes["from"] = fro
		if ID:
			el.attributes["id"] = ID
		el.attributes["type"] = "error"
		error = el.addElement("error")
		error.attributes["type"] = etype
		error.attributes["code"] = str(utils.errorCodeMap[condition])
		cond = error.addElement(condition)
		self.pytrans.send(el)
